package soulkun.library.utils

import androidx.lifecycle.ViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import java.io.Closeable
import java.util.HashMap
import kotlin.coroutines.CoroutineContext

/**
 * java.lang.NoSuchFieldException: No field mBagOfTags in class Lcom/soulkun/smarthome/ui/main/home/viewmodel/AppMainHomeSpeechRecognitionViewModel;
 */
private const val JOB_KEY = "androidx.lifecycle.ViewModelCoroutineScope.JOB_KEY"

public val ViewModel.dialogFragmentViewModelScope: CoroutineScope
    get() {
        return setTagIfAbsent(
            this,
            JOB_KEY,
            CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
        )
    }

internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {
    override val coroutineContext: CoroutineContext = context

    override fun close() {
        coroutineContext.cancel()
    }
}

fun <T : Any> setTagIfAbsent(viewModel: ViewModel, key: String, newValue: T): T {
    val clazz = viewModel::class.java
    val tagField = clazz.getDeclaredField("mBagOfTags")
    tagField.isAccessible = true
    val mBagOfTags = tagField.get(viewModel) as HashMap<String, Any>
    synchronized(mBagOfTags) {
        mBagOfTags.put(key, newValue)
    }
    val clearedField = clazz.getDeclaredField("mCleared")
    clearedField.isAccessible = true
    val mCleared = clearedField.get(viewModel) as Boolean
    if (mCleared) {
        // It is possible that we'll call close() multiple times on the same object, but
        // Closeable interface requires close method to be idempotent:
        // "if the stream is already closed then invoking this method has no effect." (c)
        val declaredMethod =
            clazz.getDeclaredMethod("closeWithRuntimeException", Object::class.java)
        declaredMethod.invoke(viewModel, newValue)
    }
    return newValue
}